#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>

#include <alloca.h>
#include <string.h>

// for gettimeofday
#include <sys/time.h>

// for W_OK
#include <unistd.h>

#include <dirent.h>

#include <iostream>
#include <sstream>

#include <list>

#include "srp.h"

#include "c_client.h"
#include "c_server_thread.h"
#include "c_config.h"




namespace dhd
{
namespace client
{
   
   Client * g_client;

   

   void handle_fuse_request( const char * path, PInteraction & inter )
   {
       g_client->handle_fuse_request( path, inter );
   }


   Client::Client( const std::string & _config_file ) :
       config_file(_config_file), config(0)
   {
       struct timeval t;
       
       gettimeofday( &t, 0 );
       
       start_sec  = t.tv_sec;
       start_nsec = t.tv_usec * 1000;
       
       srp_init(0,0);
       
       
       // throws ConfigurationError on failure
       config       = new ClientConfig( config_file );
       
       
       std::cout << "--------------------------------------" << std::endl;
       std::cout << "- Server Configuration" << std::endl;
       
       BOOST_FOREACH( ServerCFG s, config->servers )
       {
           std::cout << "Host: " << s.hostname << "  Port: " << s.port << " SSL: " << s.use_ssl << " SRP: " << s.use_srp << std::endl;
           
           std::string key = s.hostname + ":" + boost::lexical_cast<std::string>( s.port );
                      
           server_map[ key ] = new ServerThread( s );
       }
       
       std::cout << "--------------------------------------" << std::endl;
   }


   Client::~Client()
   {
       std::cerr << "Waiting for background threads to exit\n";
       
       server_map.clear(); // Shutdown all ServerThreads
       
       std::cerr << "Background threads exited!\n";
       
       srp_fini();
   }



   void Client::handle_fuse_request( const char * path, PInteraction & inter )
   {
       std::cerr << "-------------------------------------------------------------" << std::endl;
       std::cerr << "REQ:     " << inter->request.ShortDebugString() << std::endl;
    
    
       // Root directrory operation
       if ( path[0] == '/' && path[1] == '\0' )
       {
           std::cerr << "Union Operation!" << std::endl;
           
           handle_union_request( inter );
       }
       else if ( strcmp(path, "/dhd_fs_status") == 0 )
       {
           handle_status_request( inter );
       }
       else
       {
           ServerThread * owner = get_server_for( path );
           
           const char * r = owner ? "true" : "false";
           
           std::cerr << "GOT SERVER: " << r << " For path: " << path << std::endl;
           
           if ( owner )
           {
               owner->queue_interaction( inter );
               
               if (inter->reply_required())
                   inter->wait_for_reply();
               
               std::cerr << "RESULT: " << inter->reply.ShortDebugString() << std::endl;
           }
           else
           {
               inter->reply.set_msg_type( inter->request.msg_type() );
               
               if (inter->request.msg_type() == dhd::GETATTR)
                   
                   inter->reply.set_result( -ENOENT );
               else
                   inter->reply.set_result( -EACCES );
               
               std::cerr << "NO OWNER FOR PATH: " << path << std::endl;
           }
       }
   }
   
   //-----------------------------------------------------------------------------
   // Union Methods
   //-----------------------------------------------------------------------------


   ServerThread * Client::get_server_for( const char * path )
   {
       char         buff[1024];
       char       * pend = buff + sizeof(buff);
       const char * pp   = path + 1; // strip off the leading '/'
       char       * pb   = buff;
       
       
       while( pb != pend )
       {
           *pb = *pp;
           
           if (*pb == '/')
           {
               *pb = '\0';
               break;
           }
           ++pb;
           ++pp;
       }
       
       if ( pb == pend )
           return 0; // Path too long
       
       boost::mutex::scoped_lock guard( client_mutex );
       
       std::cerr << "SEARCHING PATH: raw = #" << path <<"#  Cooked = #" << buff << "#" << std::endl;
       
       UnionMap::iterator i = union_map.find( buff ); // strip off the leading '/'
       
       if ( i == union_map.end() )
           return 0; // Not found
       else
           return i->second.server;
   }
   

   void Client::add_dir( const std::string & name, int mode, int inode, ServerThread * server )
   {
       boost::mutex::scoped_lock guard( client_mutex );
       
       Client::UnionEntry ue = { name, mode, inode, server };
       
       union_map[ name ] = ue;
   }


   void Client::del_dir( const std::string & name )
   {
       boost::mutex::scoped_lock guard( client_mutex );
       
       union_map.erase( name );
   }

   
   void Client::del_all( ServerThread * server )
   {
       boost::mutex::scoped_lock guard( client_mutex );
       
       typedef std::list< std::string > SMap;
       
       SMap del_names;
       
       for (UnionMap::iterator i = union_map.begin(); i !=  union_map.end(); i++ )
           if ( i->second.server == server )
               del_names.push_back( i->second.name );
       
       for ( SMap::iterator x = del_names.begin(); x != del_names.end(); x++ )
           union_map.erase( *x );
   }
   

   std::string Client::get_status()
   {
       std::stringstream o;
       
       for( std::set< ServerCFG >::iterator i = config->servers.begin();
            i != config->servers.end(); i++ )
       {
           std::string key = i->hostname + ":" + boost::lexical_cast<std::string>( i->port );
        
           PServerThread s = server_map[ key ];
                
           if (s)
           {
               if (s->is_connected()) 
                   o << "Connected    ";
               else
                   o << "Disconnected ";
               
               o << "Server: " << i->hostname << ":" << i->port << " ";
               
               if ( i->use_ssl )
                   o << "SSL:Enabled ";
               else
                   o << "SSL:Disabled ";
               
               if ( i->use_srp )
                   o << "SRP_Authentication:Enabled ";
               else
                   o << "SRP_Authentication:Disabled ";
           }
           else
           {
               o << "FAILED TO FIND SERVER!!!!! : " << i->hostname;
           }
           o << std::endl;
       }
       
       return o.str();
   }


   // This method fakes a /dhd_fs_status file in the root directory. The only
   // operations we allow are reading and obtaining basic stat information.
   // 
   void Client::handle_status_request( PInteraction & inter )
   {
       inter->reply.set_msg_type( inter->request.msg_type() );

       const char * op_name = 0;
    
       int (Client::* fp)( PInteraction & ) = & Client::union_ignore; 
    
       switch( inter->request.msg_type() )
       {
         case dhd::GETATTR:
           {
               dhd::Stat *s = inter->reply.mutable_fstat();
               
               s->set_st_size    ( get_status().length() );
               s->set_st_dev     ( 0 );
               s->set_st_ino     ( 2 );
               s->set_st_nlink   ( 1 );
               s->set_st_mode    ( 33060 ); // regular file with permissions 444
               s->set_st_uid     ( 0 );
               s->set_st_gid     ( 0 );
               s->set_st_rdev    ( 0 );
               s->set_st_blksize ( 4096 ); // "Prefered" write size for the filesystem. We may want to tweak this
               s->set_st_blocks  ( 0 );
               s->mutable_st_atimespec()->set_tv_sec  ( 0 );
               s->mutable_st_atimespec()->set_tv_nsec ( 0 );
               s->mutable_st_mtimespec()->set_tv_sec  ( start_sec);
               s->mutable_st_mtimespec()->set_tv_nsec ( start_nsec);
               s->mutable_st_ctimespec()->set_tv_sec  ( start_sec);
               s->mutable_st_ctimespec()->set_tv_nsec ( start_nsec);
               
               inter->reply.set_result( 0 );
           }
           return;
           
         case dhd::ACCESS:
           {
               inter->reply.set_result( inter->request.access_mask() & W_OK == 0 ? 0 : -EACCES );
           }
           return;
           
           
         case dhd::OPEN:
           {
               inter->reply.set_open_id( 4 );
               inter->reply.set_result( 0 );
           }
           return;
           
         case dhd::RELEASE:
           {
               inter->reply.set_result( 0 );
           }
           return;
           
         case dhd::READ:
           {
               char sbuff[4096];
               char *start = sbuff + inter->request.offset();
               std::string status = get_status();
               
               strncpy(sbuff, status.c_str(), sizeof(sbuff));
               
               int  total_len = strlen(sbuff);
               
               if ( start >= &sbuff[ total_len ] )
               {
                   inter->reply.set_result(0);
                   std::cout << "STATUS READ DONE " << std::endl;
               }
               else
               {
                   inter->pbuffer = allocate_buffer( inter->request.size() );
                   
                   int ncpy = inter->request.size() < inter->pbuffer->max_size() ? inter->request.size() : inter->pbuffer->max_size();
                   
                   int slen = strlen(start);
                   
                   if ( slen < ncpy )
                       ncpy = slen;
                   
                   memcpy( inter->pbuffer->data(), start, ncpy );
                   
                   inter->pbuffer->set_size( ncpy );
                   
                   std::cout << "STATUS READ: " << " req size = " << inter->request.size() << " req off = " << inter->request.offset() << " actual = " << ncpy << std::endl;
                   
                   inter->reply.set_result( ncpy );
               }
           }
           return;
           
         case dhd::READ_DIR:    if (!op_name) op_name = "readdir";
         case dhd::CHMOD:       if (!op_name) op_name = "chmod";
         case dhd::CHOWN:       if (!op_name) op_name = "chown";
         case dhd::STATFS:      if (!op_name) op_name = "statfs";
         case dhd::FSYNC:       if (!op_name) op_name = "fsync";
           
         case dhd::SETXATTR:    if (!op_name) op_name = "setxattr";
         case dhd::GETXATTR:    if (!op_name) op_name = "getxattr";
         case dhd::LISTXATTR:   if (!op_name) op_name = "listxattr";
         case dhd::REMOVEXATTR: if (!op_name) op_name = "removexattr";
           
         case dhd::READ_LINK:   if (!op_name) op_name = "readlink"; 
         case dhd::MKNOD:       if (!op_name) op_name = "mknod"; 
         case dhd::MKDIR:       if (!op_name) op_name = "mkdir"; 
         case dhd::UNLINK:      if (!op_name) op_name = "unlink"; 
         case dhd::RMDIR:       if (!op_name) op_name = "rmdir"; 
         case dhd::SYMLINK:     if (!op_name) op_name = "symlink"; 
         case dhd::RENAME:      if (!op_name) op_name = "rename"; 
         case dhd::LINK:        if (!op_name) op_name = "link"; 
         case dhd::TRUNCATE:    if (!op_name) op_name = "truncate"; 
         case dhd::UTIMENS:     if (!op_name) op_name = "utimens"; 
         case dhd::WRITE:       if (!op_name) op_name = "write";
           
         default:
           if (!op_name) op_name = "UNKNOWN";
       }
       std::cout << "*** IGNORING dhd_fs_status OPERATION: " << op_name << std::endl;
       inter->reply.set_result( -EACCES );
   }
   
   


   // These methods populate the inter result object in the same way as if it were
   // received from the network. 
   // 
   void Client::handle_union_request( PInteraction & inter )
   {
       inter->reply.set_msg_type( inter->request.msg_type() );

       const char * req_name = 0;
    
       int (Client::* fp)( PInteraction & ) = & Client::union_ignore; 
    
       switch( inter->request.msg_type() )
       {
         case dhd::GETATTR:     if (!req_name) req_name = "getattr";     fp = & Client::union_getattr;     break;
         case dhd::ACCESS:      if (!req_name) req_name = "access";      fp = & Client::union_access;      break;
         case dhd::READ_DIR:    if (!req_name) req_name = "readdir";     fp = & Client::union_readdir;     break;
         case dhd::CHMOD:       if (!req_name) req_name = "chmod";       fp = & Client::union_chmod;       break;
         case dhd::CHOWN:       if (!req_name) req_name = "chown";       fp = & Client::union_chown;       break;
         case dhd::STATFS:      if (!req_name) req_name = "statfs";      fp = & Client::union_statfs;      break;
         case dhd::FSYNC:       if (!req_name) req_name = "fsync";       fp = & Client::union_fsync;       break;
           
         case dhd::SETXATTR:    if (!req_name) req_name = "setxattr";    fp = & Client::union_setxattr;    break;
         case dhd::GETXATTR:    if (!req_name) req_name = "getxattr";    fp = & Client::union_getxattr;    break;
         case dhd::LISTXATTR:   if (!req_name) req_name = "listxattr";   fp = & Client::union_listxattr;   break;
         case dhd::REMOVEXATTR: if (!req_name) req_name = "removexattr"; fp = & Client::union_removexattr; break;
           
         case dhd::READ_LINK:   if (!req_name) req_name = "readlink"; 
         case dhd::MKNOD:       if (!req_name) req_name = "mknod"; 
         case dhd::MKDIR:       if (!req_name) req_name = "mkdir"; 
         case dhd::UNLINK:      if (!req_name) req_name = "unlink"; 
         case dhd::RMDIR:       if (!req_name) req_name = "rmdir"; 
         case dhd::SYMLINK:     if (!req_name) req_name = "symlink"; 
         case dhd::RENAME:      if (!req_name) req_name = "rename"; 
         case dhd::LINK:        if (!req_name) req_name = "link"; 
         case dhd::TRUNCATE:    if (!req_name) req_name = "truncate"; 
         case dhd::UTIMENS:     if (!req_name) req_name = "utimens"; 
         case dhd::OPEN:        if (!req_name) req_name = "open"; 
         case dhd::READ:        if (!req_name) req_name = "read"; 
         case dhd::WRITE:       if (!req_name) req_name = "write"; 
         case dhd::RELEASE:     if (!req_name) req_name = "release"; 
           
         default:
           std::cout << "*** IGNOREING TOP-LEVEL OPERATION: " << req_name << std::endl;
       }
    
       inter->reply.set_result( (this->*fp)( inter ) );
   }


   int Client::union_ignore( PInteraction & inter )
   {
       return -EFAULT;
   }


   int Client::union_readdir( PInteraction & inter )
   {
       boost::mutex::scoped_lock guard( client_mutex );
 
       dhd::DirEntry * rde = 0;
       
       for (UnionMap::iterator i = union_map.begin(); i != union_map.end(); i++ )
       {
           rde = inter->reply.add_dir_entries();
           
           rde->set_name(  i->second.name );
           rde->set_inode( i->second.inode );
           rde->set_mode(  i->second.mode << 12 );
       }
       
       // TODO: provide true inode for '..'
       // 
       rde = inter->reply.add_dir_entries();
       
       rde->set_name(  "." );
       rde->set_inode( 1 );
       rde->set_mode(  DT_DIR << 12 );
       
       rde = inter->reply.add_dir_entries();
       
       rde->set_name(  ".." );
       rde->set_inode( 0 );
       rde->set_mode(  DT_DIR << 12 );
       
       rde = inter->reply.add_dir_entries();
       
       rde->set_name(  "dhd_fs_status" );
       rde->set_inode( 2 );
       rde->set_mode(  33060 );
           
       return 0;
   }
   

   int Client::union_getattr ( PInteraction & inter )
   {
       dhd::Stat *s = inter->reply.mutable_fstat();
       
       s->set_st_size    ( 4096 );
       s->set_st_dev     ( 0 );
       s->set_st_ino     ( 1 );
       s->set_st_nlink   ( 2 );
       s->set_st_mode    ( 16749 ); // Directory is read + execute for all and writeable by none
       s->set_st_uid     ( 0 );
       s->set_st_gid     ( 0 );
       s->set_st_rdev    ( 0 );
       s->set_st_blksize ( 4096 ); // "Prefered" write size for the filesystem. We may want to tweak this
       s->set_st_blocks  ( 8 );
       s->mutable_st_atimespec()->set_tv_sec  ( 0 );
       s->mutable_st_atimespec()->set_tv_nsec ( 0 );
       s->mutable_st_mtimespec()->set_tv_sec  ( start_sec);
       s->mutable_st_mtimespec()->set_tv_nsec ( start_nsec);
       s->mutable_st_ctimespec()->set_tv_sec  ( start_sec);
       s->mutable_st_ctimespec()->set_tv_nsec ( start_nsec);
       
       return 0;
   }


   int Client::union_access  ( PInteraction & inter )
   {
       return inter->request.access_mask() & W_OK == 0 ? 0 : -EACCES;
   }


   int Client::union_chmod   ( PInteraction & inter )
   {
       return -EACCES;
   }

   
   int Client::union_chown   ( PInteraction & inter )
   {
       return -EACCES;
   }


   namespace
   {
      
      // This runs within the context of the Server Thread in the normal case. If the server is disconnected,
      // it will run in the context of the calling thread.
      void statfs_result_helper(boost::mutex *amutex, boost::condition_variable *acv, int *npending_replies,
                                const ToClient &)
      {       
          boost::mutex::scoped_lock guard( *amutex );
          
          --(*npending_replies);
          
          if ( *npending_replies == 0 )
              acv->notify_one();
      }
   }

   // This method runs within the context of the FUSE thread. It creates a copy of the PInteraction
   // for each server, sends it, and waits for all replies to be received. ServerThreads that are
   // disconnected at the time of the call or become disconnected before the reply is received will
   // return -EFAULT as the reply status. For connected servers, the 'statfs_result_helper' callback
   // will execute within the context of the ServerThread thread. The callback is executed immediately
   // for ServerThreads that are disconnected when 'queue_pending' is invoked.
   int Client::union_statfs  ( PInteraction & inter )
   {
       typedef std::list< PInteraction > ResultList;
       
       ResultList                 rlist;
       boost::mutex               amutex;
       boost::condition_variable  acv;
       int                        npending_replies = server_map.size();
       AsyncCompleteHandler       callback;
       
       callback = boost::bind(statfs_result_helper, &amutex, &acv, &npending_replies, _1); 
       
       
       for ( ServerMap::iterator i = server_map.begin(); i != server_map.end(); i++ )
       {
           PInteraction pi = allocate_interaction();
           
           pi->request                   = inter->request;
           pi->reply_error_on_disconnect = true;
           
           pi->set_async_complete_callback( callback );
           
           rlist.push_back( pi );
           
           i->second->queue_interaction( pi );
       }
       
       
       { // Wait for all replies
           boost::mutex::scoped_lock guard( amutex );
           
           while (npending_replies != 0)
               acv.wait( guard );
       }
       
       int blocks = 0;
       int bfree  = 0;
       int bavail = 0;
    
       for ( ResultList::iterator x = rlist.begin(); x != rlist.end(); x++ )
       {
           if ( (*x)->reply.result() != -EFAULT && (*x)->reply.has_vstat() )
           {
               blocks += (*x)->reply.vstat().f_blocks();
               bfree  += (*x)->reply.vstat().f_bfree();
               bavail += (*x)->reply.vstat().f_bavail();
           }
       }
    
       ::dhd::StatVFS* v = inter->reply.mutable_vstat();
       
       inter->reply.set_result(0);
       
       v->set_f_bsize  ( 4096 );
       v->set_f_frsize ( 4096 );
       v->set_f_blocks ( blocks ); // hardcoding to 1000000 works for testing
       v->set_f_bfree  ( bfree  ); // ''
       v->set_f_bavail ( bavail ); // ''
       v->set_f_files  ( 1000 );
       v->set_f_ffree  ( 1000 );
       v->set_f_favail ( 1000 );
       
       return 0;
   }


   int Client::union_fsync   ( PInteraction & inter )
   {
       return 0;
   }
   
   
   int Client::union_mknod   ( PInteraction & inter )
   {
       return -EACCES;
   }
   
   
   int Client::union_getxattr    ( PInteraction & inter )
   {
       return 0;
   }
   
   
   int Client::union_setxattr    ( PInteraction & inter )
   {
       return 0;
   }
   
   
   int Client::union_listxattr   ( PInteraction & inter )
   {
       return 0;
   }
   
   
   int Client::union_removexattr ( PInteraction & inter )
   {
       return 0;
   }

}// end namespace dhd::client
}// end namespace dhd

